/* Class: UWA.Widget

The Module class provides abstract methods to create and manipulate Netvibes modules.
Once created, it must be registered into an execution environnement to be runned.

_Copyright 2006-2007 Netvibes. All rights reserved._

Example:
Usually, the easiest way to create a Module is to get it from the Environment
(start code)
...
var aModule = Environment.getModule();
...
(end)

*/

if (typeof UWA == "undefined") var UWA = {};

UWA.Widget = function() {
    
    /* Property: id 

    *String*: Unique identifier of the module.
    
    The value depends on the execution environment: the Environment registration handler sets this property.
    */
    this.id = '';

    /* Property: environment

    *Object*: Reference to the execution environnement.
    
    The Environment registration handler sets this property.
    */
    this.environment = null;

    /* Property: data
  
    *Collection{Object}*: Stores module's data (defined in the skeleton properties).
    
    This property can be modified by the "<setValue>" method, and accessed by the "<getValue>" method.
    */
    this.data = {};

    /* Property: callbacks
    
    *Collection{Object}*: Module's callback methods.
    
    The collection is initially empty. Callback methods are added by the "<setCallback>" method, and are launched by the "<callback>" method.
    */
    this.callbacks = {};

    /* Property: preferences
    
    *Array[Object]*: Stores module's preferences. 
    
    The array is initially empty. It is initialised by the "<setPreferences>" method.
    */
    this.preferences = [];
    
    /* to document */
    this.periodicals = {};
    
    /* Property: searchResultCount
    
    *Integer*: the search result count when the module is onSearch.
    
    This property is set by the "<setSearchResultCount>" method.
    */
    this.searchResultCount = 0;
    
    /* Property: unreadCount
    
    *Integer*: the count of unread items in the module.
    
    The unread count is set by the "<setUnreadCount>" method.
    */
    this.unreadCount = 0;
    
    /* Property: prefsForm
    
    *Object*: the preference's form of the module.
    
    */
    /* deprecated */
    this.prefsForm = null;
    
    /* Property: title
    
    *String*: module's title.
    
    The title of the module. It is set by the "<setTitle>" method.
    */
    this.title = '';
    
    /* Property: debugMode
    
    *Boolean*: activates or desactivates the debug mode for the module. 
    
    The default value is TRUE. When TRUE, messages written with "widget.log" will appear in the console.
    */
    this.debugMode = false;
    
    /* to document */
    this.elements = {};
    
    /* to document */
    this.metas = {};
    
    /* deprecated */
    this.inline = false;
    
    /* to document */
    this.apiVersion = '1.0';
    
    /* to document */
    this.lang = 'en_US';
    
    /* to document */
    this.locale = 'us';
    
    /* to document */
    this.dir = 'ltr';
    
    if(this.initialize) this.initialize();

}

UWA.Widget.prototype = {
  
  /* Method: setTitle
  
  Sets the title of the Module.
  
  Parameters:
  * String - title: The title of the module. 
  
  Returns:
  * Nothing, but calls the method associated with the "onUpdateTitle" callback. 
  The callback method is set with the "<setCallback>" method.
  
  Example:
  In the HTML skeleton of the module:
  (start code)
  ...
  widget.debugMode = true;
  ...
  var myUpdateTitle = function () {
    widget.log("onUpdateTitle was called");
  }
  ...
  widget.setCallback("onUpdateTitle", myUpdateTitle);
  ...
  widget.setTitle('New title');
  ...
  (end)
  The console should display "onUpdateTitle was called" after the "setTitle" method was called.
  */
  setTitle: function(title, extended) {
    this.title = title;
    if (this.elements['title']) {
      if (extended) {
         extended = ' ' + extended + '';
       } else {
         extended = '';
       }
      this.elements['title'].setHTML(title + extended);
    }
    if (this.environment && this.environment.setTitle) this.environment.setTitle(title);
  },
  
  /* to document */
  getTitle: function() {
    if (this.environment && this.environment.getTitle) return this.environment.getTitle();
    return this.title.stripTags(); // stripTags = prototype.js
  },
  
  /* Method: setBody
  
  Sets the body of the Module. Erases the previous body.
  
  Parameters:
  * Object - content: The body of the module.
  
  Returns:
  * Nothing, but calls the methods associated with the "onUpdateBody" callback. 
  For backward compatibility, it also calls the method associated with the *DEPRECATED* "onUpdateContent" callback.
  Those methods must be defined in the HTML skeleton of the Module.
  Callback methods are set with the "<setCallBack>" method.
  
  Example: 
  See the exemple of <setTitle> for callback assignation
  */
  setBody: function(content) {
    this.body.setContent(content);
    this.callback('onUpdateBody');
  },
  
  /* Method: addBody
  
  Adds contents to the existing body of the Module.
  
  Parameters:
  * Object - content: The content to add in the body of the module.
  
  Returns:
  * Nothing, but calls the methods associated with the "onUpdateBody" callback. 
  This method must be defined in the HTML skeleton of the Module.
  Callback methods are set with the "<setCallBack>" method.
  
  Example: 
  See the exemple of <setTitle> for callback assignation
  */
  addBody: function(content) {
    this.body.addContent(content);
    this.callback('onUpdateBody');
  },

  /* Method: setIcon
  
  Sets the icon for the Module.
  
  Parameters:
  * String - URL: the URL pointing to the ICON. The URL must include the protocol (http://)
  
  Returns:
  * Nothing.
  
  Examples:
  (start code)
  ...
  this.setIcon("http://www.netvibes.com/favicon.ico");
  ...
  (end)
  */
  setIcon: function(url) {
    if (this.environment.setIcon) this.environment.setIcon(url);
    else if(this.elements['icon']) this.elements['icon'].setHTML('<img width="16" height="16" src="' + url + '" />');
  },
  
  /* deprecated */
  setElementId: function(element, id) {
    this.log('widget.setElementId is deprecated');
    this.$(element).setAttribute('id', 'm_' + this.id + '_' + id);
  },

  /* Method: createElement
  
  Creates a new element according to the provided "tagName".
  
  Parameters:
  * String - tagName: the HTML tag name of the element to create.
  
  Returns:
  * Element: The created element.
  
  Example:
  (start code)
  ...
  aDiv = this.createElement('div');
  ...
  (end)
  aDiv is now the Element object for an HTML DIV tag.
  */
  createElement: function(tagName, elName) {
    if (tagName == 'script' ) return false; // blacklist here
    var el = document.createElement(tagName);
    if (typeof elName == 'string') {
      this.elements[elName] = el;
    } 
    return UWA.$element(el);
  },

  /* deprecated */
  $: function(el) {
    this.log('widget.$ is deprecated');
    if (typeof el == 'string' && this.elements[el]) {
      el = this.elements[el];
    }
    return UWA.$element(el);
  },

  /* Method: initPreferences
  
  Initializes preferences of the module. The method gets values from the environnement.
  We values do not exist in the environment, it sets them to their default values.
  
  This method is called by the "<launch>" method of the Module, and when the module is registered in the Environment.
  
  Parameters:
  * None.
  
  Returns:
  * Nothing.
  */
  initPreferences: function() {
    for(var i = 0; i < this.preferences.length; i++) {
      var pref = this.preferences[i];
      if (typeof pref.name == "undefined") continue; // no name = preference ignored
      if (pref.defaultvalue) pref.defaultValue = pref.defaultvalue; // fix after xml parsing
      var value = this.getValue(pref.name);
      if (value == undefined || value == null || value == 'undefined') { // the last not really needed but could help
        if (typeof pref.defaultValue != "undefined") this.data[pref.name] = pref.defaultValue;
        else this.data[pref.name] = null;
      }
      if(pref.defaultValue && value && pref.defaultValue == value) {
        if (this.environment && this.environment.deleteData) this.environment.deleteData(pref.name);
      }
    }
  },
  
  /* to document */
  getPreference: function(name) {
    for(var i = 0; i < this.preferences.length; i++) {
      if(this.preferences[i].name == name) return this.preferences[i];
    }
    return null;
  },
  
  /* Method: setPreferences
  
  Sets preferences of the module. Replaces previous preferences.
  
  Parameters:
  * Array[Collection{String}] - schema: the set of preferences, as an array, for the module.

  Returns:
  * Nothing.
  */
  setPreferences: function(schema) {
    this.preferences = schema;
  },
  
  /* Method: addPreference
  
  Adds a single preferences to the existing preferences of the module.
  
  Parameters:
  * Collection{String} - preference: the set of preferences, as a collection, for the module. 
  A preference is a collection of Strings, with one entry. 
  The key is the name of the preference, and the value is the value of the preference.

  Returns:
  * Nothing.
  
  Example:
  (start code)
  ...
  aPreference = {};
  aPreference['name'] = "offset";
  widget.addPreference(aPreference);
  ...
  (end)
  */
  addPreference: function(preference) {
    this.preferences.push(preference);
  },
  
  /* to document */
  setPreferencesXML: function(prefs) {
    this.setPreferences( [] ); // empty preferences array
    for(var i = 0; i < prefs.length; i++) {
      var preference = {};
      for(var j = 0; j < prefs[i].attributes.length; j++) {
        var name = prefs[i].attributes[j]['nodeName'];
        var value = prefs[i].attributes[j]['nodeValue'];
        preference[name] = value;
      }
      if (preference.type == 'list') {
        var options = prefs[i].getElementsByTagName("option");
        preference.options = [];
        for(var j = 0; j < options.length; j++) {
          var option = {};
          if ( options[j].attributes[0]['value'] ) option[options[j].attributes[0]['name']] = options[j].attributes[0]['value'];
          if ( options[j].attributes[1]['value'] ) option[options[j].attributes[1]['name']] = options[j].attributes[1]['value'];
          preference.options.push(option)
        }
      }
      this.addPreference(preference);
    }
  },
  
  /* to document */
  setMetasXML: function(metas) {
    var metasArray = [];
    for(var i = 0; i < metas.length; i++) {
      if(metas[i].name) var name = metas[i].name;
      else var name = metas[i].attributes[0]['nodeValue'];
      if(metas[i].content) var value = metas[i].content;
      else var value = metas[i].attributes[1]['nodeValue'];
      if(value == 'false') value = false; else if(value == 'true') value = true; // booleanise
      metasArray[name] = value;
    }
    this.setMetas(metasArray);
  },
  
  /* to document */
  setMetas: function(metas) {
    this.metas = metas;
    if(this.metas.debugMode) this.setDebugMode(this.metas.debugMode);
    if(this.metas.autoRefresh) this.setAutoRefresh(this.metas.autoRefresh);
  },
  
  /* Method: getValue
  
  Gets the value of the given preference of the module.
  *Todo*: maybe we can force the type against preferences?
  
  Parameters:
  * String - name: the name of the preference we want the value of.
  
  Returns:
  * Nothing.
  */
  getValue: function(name) {
    if (this.data[name] != undefined) return this.data[name];
    if(this.environment && this.environment.getData) {
      var value = this.environment.getData(name);
      if (value == 'null') value = null;
      this.data[name] = value;
      return value;
    }
    return null;
  },
  
  /* to document */
  getInt: function(name) {
    var value = this.getValue(name);
    if(value == 'true' || value == true) value = 1;
    value = parseInt(value, 10);
    return isNaN(value) ? 0 : value;
  },
  
  /* to document */
  getBool: function(name) {
    return this.getInt(name) ? true : false;
  },
  
  /* Method: setValue
  
  Sets the value of the given preference of the module. 
  It sets the value internally first, and then calls the Environnement dedicated function.
  
  Parameters:
  * String - name: the name of the preference we want to set.
  * Object - value: the value of the preference (String, Integer, etc.)
  
  Returns:
  * Object: the value of the preference we set.
  */
  setValue: function(name, value) {
    this.data[name] = value;
    var pref = this.getPreference(name);
    // if(pref && pref.defaultValue && pref.defaultValue == value) {
    // if (this.environment && this.environment.deleteData) this.environment.deleteData(name);
    // } else {
      if (this.environment && this.environment.setData) this.environment.setData(name, value);
    // }
    return value;
  },
  
  /* to document */
  saveValues: function(callback) {
    if (this.environment && this.environment.saveDatas) {
      this.environment.saveDatas(callback);
    } else {
      callback();
    }
  },

  /* Method: setAutoRefresh
  
  Sets the auto-refresh interval for the module. 
  The module must define a "onRefresh" method for the auto-refresh to work properly.
  
  Parameters:
  * Integer - delay: the refresh delay, in *milliseconds*.
  
  Returns:
  * Nothing.
  
  Example:
  (start code)
  ...
  widget.setAutoRefresh(20); // Set the auto-refresh interval to 20 minutes
  ...
  (end)
  */
  setAutoRefresh: function(delay) {
    var rndUpdateTime = Math.round(10*1000*Math.random());
    if (this.onRefresh) {
      delay = parseInt(delay) * 1000 * 60; // from minutes to milliseconds
      this.setPeriodical('autoRefresh', this.onRefresh, delay + rndUpdateTime);
    }
  },
  
  /* to document */
  setPeriodical: function(name, fn, delay, force) {
    this.clearPeriodical(name);
    this.periodicals[name] = setInterval(fn.bind(this), delay);
    if (force) fn();
  },
  
  /* to document */
  clearPeriodical: function(name) {
    if (this.periodicals[name]) { clearInterval(this.periodicals[name]) }
  },

  /* Method: setCallback
  
  Associates a function to a callback key.
  
  Parameters:
  * String - name: the name of the callback (e.g. "onUpdateTitle"),
  * Function - fn: the function to call when the callback key is used.

  Returns:
  *Nothing.
  
  Example:
  See the example of the "<setTitle>" method.
  */
  setCallback: function(name, fn) {
    this.callbacks[name] = fn;
  },
  
  /* Method: callback
  
  Executes the callback method associated with the given callback name (key). 
  Returns false if no callback method is associated with the given key.
  
  *Notes*: would be good to allow more parameters through apply() style function
  
  Parameters:
  * String - name: the callback name (e.g. "onUpdateTitle");
  * Object
  
  Returns:
  * Nothing, but calls the method associated with the given callback name (key) 
  * *OR* a Boolean set to False, if no method is found for the given callback name.
  */
  callback: function(name, args, bind) {
    if (typeof bind == 'undefined') bind = this;
    try {
      if (this[name]) this[name].apply(bind, [args]);
      if (this.callbacks[name]) this.callbacks[name].apply(bind, [args]);
    } catch(e) {
      UWA.log(e);
    }
    if(this.environment && this.environment.callback) this.environment.callback(name); 
  },
  
  /* Method: setSearchResultCount
  
  Sets the search result count.
  
  Parameters:
  * Integer - count: the count of results for the current search terms.
  
  Returns:
  * Nothing, but updates the title with the result count, if greater or equal to zero.
  */
  setSearchResultCount: function(count) {
    this.searchResultCount = count;
    if (this.environment.setSearchResultCount) this.environment.setSearchResultCount(count);
    if (count > 0) this.setTitle(this.title, '<span class="result">(' + count + ')</span>');
    else if (count === 0) this.setTitle(this.title, '<span class="result">(' + _("no results") + ')</span>');
    else this.setTitle(this.title);
  },

  /* Method: setUnreadCount 
  
  Sets the count of unread items.
  
  Parameters:
  * Integer - count: the count of unread items.
  
  Returns:
  * Nothing, but updates the title with the unread count, if greater or equal to zero.
  */  
  setUnreadCount: function(count) {
    this.unreadCount = count;
    if (this.environment && this.environment.setUnreadCount) this.environment.setUnreadCount(count);
  },

  /* Method: launch
  
  Launches the module. In principle, this method is not called directly. 
  It is called by the Environment, as it sets a number of other necessary requirements to launch the module.
  
  Parameters:
  * None.
  
  Returns:
  * Nothing.
  */
  launch: function() {
    this.initPreferences();
    this.callback('onLoad');
  },
  
  /* Method: log
  
  Logs modules message in the console, if one exists and if the "<debugMode>" is true.
  It is compliant with Firebug, Safari and Opera.
  *Experimental - Testing*
  
  Parameters:
  * String - string: the string to display in the console.
  
  Returns:
  * Nothing.
  */
  log: function(string) {
    if (this.debugMode) UWA.log(string);
  },
  
  /* to document */
  onEdit: function() {
    
    if (this.prefsForm) {
      var form = this.prefsForm;
    } else {
      var prefsForm = new UWA.Controls.PrefsForm( { module: this } );
      var form = prefsForm.getContent();
    }
    
    this.elements['edit'].setContent(form);
    
    var infos = this.getInfos();
    if(infos) this.elements['edit'].addContent(infos);

    // Fire "ShowEdit" notification with HTMLDivElement "edit" as argument
    this.callback('onShowEdit', this.elements['edit']);

    this.elements['edit'].show();
  },
  
  /* to document */
  getInfos: function() {
    if(this.metas['author']) {
      var p = this.createElement('p');
      p.setStyle('padding', '10px');
      p.setStyle('text-align', 'right');
      if(this.metas['website']) {
        var content = 'Widget by <strong><a href="' + this.metas['website'] + '" rel="author">' + this.metas['author'] + '</a></strong>';
      } else {
        var content = 'Widget by <strong>' + this.metas['author'] + '</strong>';
      }
      if(this.metas['version']) {
        content += ' - version <strong>' + this.metas['version'] + '</strong>';
      }
      p.setHTML(content);
      return p;
    }
    return false;
  },
  
  /* to document */
  endEdit: function() {
    this.elements['edit'].hide();
    if (this.onRefresh) this.onRefresh();
    else if (this.onLoad) this.onLoad();
    this.callback('onHideEdit');
  },
  
  /* to document */
  setDebugMode: function(mode) {
    if (mode) this.debugMode = true; else this.debugMode = false;
  },
  
  /* deprecated */
  setInline: function(mode) {
    if (mode) this.inline = true; else this.inline = false;    
  },
  
  /* to document */
  setCSS: function(css) {
    UWA.Utils.setCss(this.id, css);
  },
  
  /* to document */
  openURL: function(url) {
    if (this.environment && this.environment.openURL) this.environment.openURL(url); 
    else window.open(url);
  }
 
}

// old name
UWA.Module = UWA.Widget;